<!--
 * 使用xterm.js实现ssh终端编辑器
 *
 * @author Junpeng.Li
 * @date 2022-04-08 10:01:58
-->
<template>
    <div class="terminal-container"></div>
</template>

<script>
    import 'xterm/css/xterm.css'

    import {Terminal} from 'xterm'
    import {FitAddon} from 'xterm-addon-fit'

    // 防抖
    import {debounce} from 'lodash'

    import Socket from './socket'

    export default {
        name: "xTerminal",

        props: {

            /**
             * 连接信息
             */
            connInfo: {
                type: Object,
                default: () => {
                    return {
                        host: null,
                        port: null,
                        username: null,
                        password: null
                    }
                }
            },

            isActive: {
                type: Function,
                default: function (termKey) {
                    return true
                }
            }

        },

        data() {
            return {

                id: `xterm-${new Date().getTime()}`,

                /**
                 * xterm.js中Terminal对象
                 */
                term: null,

                /**
                 *
                 */
                fitAddon: null,

                /**
                 * xterm的配置
                 */
                config: {
                    // 启用时，光标将设置为下一行的开头
                    convertEol: true,

                    // 终端的回滚量
                    scrollback: 50,

                    // 是否禁止输入
                    disableStdin: false,

                    // 光标样式： 'block' | 'underline' | 'bar'
                    cursorStyle: 'bar',

                    // 光标的宽度，只有当设置cursorStyle='bar'时才会生效
                    cursorWidth: 8,

                    // 光标闪烁
                    cursorBlink: true,

                    // 字体大小
                    fontSize: 16,

                    // 字体
                    // fontFamily: '黑体'

                    // 字符间距
                    letterSpacing: 2,

                    // 行高
                    // lineHeight: 1

                    // 主题，具体属性可参考 {@link node_modules/xterm/typings/xterm.d.ts}
                    theme: {}
                },

                socket: null,

                uninstall: false
            }
        },

        mounted() {
            this.config['rows'] = this.getRows()
            this.config['cols'] = this.getCols()

            // 创建Terminal
            this.term = new Terminal(this.config)
            this.term.open(this.$el)

            this.consoleInit()

            // 让Terminal的宽度自适应
            this.fitAddon = new FitAddon()
            this.term.loadAddon(this.fitAddon)
            this.fitAddon.fit()

            // 加载socket
            this.loadSocket()

            /**
             * 监听用户按键事件
             */
            this.term.onData(key => {
                if (!this.socket || !this.socket.is()) {
                    return
                }

                this.socket.send({
                    type: 'command',
                    message: key
                })
            })

            // 监听页面大小的变化
            this.litenerResize()
        },

        methods: {

            /**
             * 添加脚本命令
             * @param command 脚本命令
             * @param isExec 是否执行
             */
            addText(command, isExec) {
                if (!this.term || !command || !this.socket) {
                    return
                }

                this.socket.send({
                    type: 'command',
                    message: command + (isExec ? '\r' : '')
                })
            },

            /**
             * 根据容器宽度计算cols
             * @returns {number}
             */
            getCols() {
                const cols = document.querySelector(".terminal-container").offsetWidth / 14
                return parseInt(cols)
            },

            /**
             * 根据容器高度计算rows
             * @returns {number}
             */
            getRows() {
                const rows = document.querySelector(".terminal-container").offsetHeight / 16 - 50
                if (rows <= 5) {
                    return 30;
                } else {
                    return parseInt(rows)
                }
            },

            /**
             * 在终端内打印初始化提示信息
             */
            consoleInit() {
                this.term.writeln('Welcome to WebSSH')
                this.term.writeln('You can click on the connect button to connect to the terminal...')
            },

            /**
             * 重置终端
             */
            reset() {
                this.term.reset()
                this.consoleInit()
            },

            /**
             * 关闭
             */
            close() {
                this.socket.close()
            },

            /**
             * 重置大小
             */
            resize() {
                try {
                    this.fitAddon.fit()

                    this.term.onResize(() => {
                        // TODO 告诉服务端列和行数被重新定义
                    })
                } catch (e) {
                }
            },

            /**
             * 监听页面发生变化
             */
            litenerResize() {
                /*
                TODO
                  记一个坑，后续找到办法再优化。过程如下：
                  当这个组件被卸载之后，window.addEventListener没有被卸载，如果再卸载时调用
                  window.removeEventListener可以移除，但是移除时函数的第二个参数为funciton，我又用到了
                  抖动函数，不知道咋写了。。尴尬。。。，目前就是定义一个uninstall变量，再使用时判断，
                  大晚上的脑子处于短路状态，等想到解决办法再改把。
                */
                window.addEventListener('resize', debounce(() => {
                    if (this.uninstall || !this.isActive(this.connInfo._key)) {
                        return
                    }

                    this.resize()
                }, 100))
            },

            /**
             * 加载WebSocket
             */
            loadSocket() {
                // 如果host为空，则不需要连接
                if (!this.connInfo.host) {
                    return
                }

                const _this = this

                this.socket = new Socket('ws://127.0.0.1:8000/ssh', {

                    onopen() {
                        _this.reset()

                        _this.term.writeln(`Connecting to ${_this.connInfo.host}:${_this.connInfo.port} ...`)

                        this.send({
                            type: 'connect',
                            message: _this.connInfo
                        })

                    },

                    onmessage(data) {
                        _this.term.write(data.trim())
                        if (data.endsWith("Connection failed.")) {
                            this.close()
                            return
                        }

                        if (data.length === 3) {
                            _this.term.write(data.trim())
                        } else {
                            _this.term.write(data)
                        }
                    },

                    afterClose() {

                    }

                }).init()
            }

        },

        /**
         * 组件销毁之前标记一下unisntall为true，表示已被卸载
         */
        beforeDestroy() {
            this.uninstall = true
        }
    }
</script>

<style>

    .terminal-container {
        width: 100%;
        height: 100%;
    }

</style>
